<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"vchao.top","root":"/","scheme":"Muse","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
<meta property="og:type" content="website">
<meta property="og:title" content="乘风破浪">
<meta property="og:url" content="http://vchao.top/index.html">
<meta property="og:site_name" content="乘风破浪">
<meta property="og:description" content="怀揣好奇，梦想改变世界的菜鸟极客">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="vchao">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://vchao.top/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>乘风破浪</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">乘风破浪</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E8%B0%88%E8%B0%88%E5%AE%89%E5%8D%93%E4%B8%ADContext%E7%9A%84%E7%90%86%E8%A7%A3[%E9%99%84%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE]/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E8%B0%88%E8%B0%88%E5%AE%89%E5%8D%93%E4%B8%ADContext%E7%9A%84%E7%90%86%E8%A7%A3%5B%E9%99%84%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE%5D/" class="post-title-link" itemprop="url">谈谈安卓中Context的理解[附思维导图]</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <blockquote>
<p>思维导图是个好东西，可以把知识串在一张图上，更便于理解记忆。</p>
</blockquote>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Context是一个抽象基类。在翻译为上下文，也可以理解为环境，提供一些程序的运行环境基础信息</p>
<h2 id="子类关系"><a href="#子类关系" class="headerlink" title="子类关系"></a>子类关系</h2><p>封装类ContextWrapper</p>
<ul>
<li>ContextThemeWrapper（带主题的封装类）<ul>
<li>Activity</li>
<li>Service</li>
<li>Application</li>
</ul>
</li>
</ul>
<p>实现类ContextImpl</p>
<h2 id="内存泄露问题"><a href="#内存泄露问题" class="headerlink" title="内存泄露问题"></a>内存泄露问题</h2><ul>
<li>静态资源导致的内存泄漏<pre><code>  解决方案：避免使用静态资源，或者使用弱引用来解决相应。
</code></pre>
</li>
<li>单例模式导致内存泄漏<pre><code>  解决方案：使用getApplicationContext()，这样使用单例与Activity就无关了，Activity释放时则不会出现内存泄露
</code></pre>
<h2 id="获取Context"><a href="#获取Context" class="headerlink" title="获取Context"></a>获取Context</h2></li>
<li>View.getContext()<pre><code>  返回当前View对象的Context对象，通常是当前正在展示的Activity对象
</code></pre>
</li>
<li>Activity.getApplicationContext()<pre><code>  获取当前Activity所在应用进程的Context对象，通常使用Context时，优先考虑全局进程Context
</code></pre>
</li>
<li>ContextWrapper.getBaseContext()<pre><code>  获取一个ContextWrapper装饰之前的Context，在实际开发中使用并不多，不建议使用
</code></pre>
</li>
<li>Activity.this <pre><code>  返回当前Activity实例，UI控件需要使用Activity作为Context对象
</code></pre>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2></li>
<li>Context共有三种类型，Application、Activity和Service<pre><code>  整个App共一个Application对象
</code></pre>
</li>
<li>Context数量 = Activity数量 + Service数量 + 1(Application)</li>
<li>三个类都属于Context，功能是由ContextImpl类去实现的</li>
<li>大多数场景下，三种类型的Context可以通用</li>
<li>特殊场景不可通用（只能使用Activity类型的Context，否则将会出错）<ul>
<li>启动Activity<pre><code>  安全考虑，Android不允许Activity或Dialog凭空出现，Activity的启动必须要建立在另一个Activity的基础上，以形成返回栈
</code></pre>
</li>
<li>弹出Dialog<pre><code>  Dialog则必须在一个Activity上面弹出（除非是System Alert类型的Dialog）
</code></pre>
<h2 id="思维导图"><a href="#思维导图" class="headerlink" title="思维导图"></a>思维导图</h2><img src="https://img-blog.csdnimg.cn/20190806225833838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly92Y2hhby5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70" alt="安卓Context的理解">    </li>
</ul>
</li>
</ul>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E8%AE%B0%E4%B8%80%E6%AC%A1%E9%80%82%E9%85%8Dbug%E8%A7%A3%E5%86%B3%E8%BF%87%E7%A8%8B%EF%BC%8C%E6%9D%83%E7%94%A8%E4%BD%9C%E7%BB%8F%E9%AA%8C%E6%95%99%E8%AE%AD%E5%88%86%E4%BA%AB/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E8%AE%B0%E4%B8%80%E6%AC%A1%E9%80%82%E9%85%8Dbug%E8%A7%A3%E5%86%B3%E8%BF%87%E7%A8%8B%EF%BC%8C%E6%9D%83%E7%94%A8%E4%BD%9C%E7%BB%8F%E9%AA%8C%E6%95%99%E8%AE%AD%E5%88%86%E4%BA%AB/" class="post-title-link" itemprop="url">记一次适配bug解决过程，权用作经验教训分享</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>特此记录经验教训用作分享，希望大家涉及到此部分时，少走弯路。</p>
<h2 id="问题描述："><a href="#问题描述：" class="headerlink" title="问题描述："></a>问题描述：</h2><ul>
<li><p>经过客户反馈，oppo，vivo若干机型无法播放视频。(代表机型oppo a59s和oppo r7s)<br>身边没有上述两个机型，采用oppo r11测试，一切正常。</p>
</li>
<li><p>无法测试，只能梳理代码，寻找原因</p>
</li>
<li><p>项目描述：采用视频播放框架Vitamio</p>
</li>
<li><p>查询两款手机系统信息。</p>
<p><strong>OPPO A59s</strong></p>
<p>出厂操作系统    ColorOS 3.0 （基于Android 5.1）<br>CPU型号    联发科 MT6750</p>
<p><strong>OPPO R7s</strong></p>
<p>出厂操作系统    Color OS 2.1（基于Android 5.1）<br>CPU型号    高通 骁龙Snapdragon MSM8939</p>
<blockquote>
<p>手机信息查询网址有很多， 在此推荐：<a target="_blank" rel="noopener" href="http://www.zol.com.cn/">http://www.zol.com.cn/</a></p>
</blockquote>
</li>
</ul>
<h2 id="解决历程："><a href="#解决历程：" class="headerlink" title="解决历程："></a>解决历程：</h2><p>（以下方法均未解决问题，此处记录仅用作发散思维）</p>
<ol>
<li><p>首先确定是否是账号问题，交叉测试后排除该原因。</p>
</li>
<li><p>查看是否是so库问题。</p>
<p>查看项目中lib库中的so文件，其中共包括armeabi, armeabi-v7a, x86, mips，分析后并无问题。</p>
<p><strong>科普：</strong></p>
<blockquote>
<p>应用程序二进制接口（Application Binary Interface）定义了二进制文件（尤其是.so文件）如何运行在相应的系统平台上，从使用的指令集，内存对齐到可用的系统函数库。在Android系统上，每一个CPU架构对应一个ABI：armeabi，armeabi-v7a，x86，mips，arm64-v8a，mips64，x86_64。</p>
<p><strong>如果项目只包含了 armeabi，那么在所有Android设备都可以运行；</strong></p>
<p><strong>如果项目只包含了 armeabi-v7a，除armeabi 架构的设备外都可以运行；</strong> </p>
<p><strong>如果项目只包含了 x86，那么armeabi架构和armeabi-v7a的Android设备是无法运行的；</strong> </p>
<p><strong>如果同时包含了 armeabi， armeabi-v7a和x86，所有设备都可以运行，</strong></p>
<p>程序在运行的时候去加载不同平台对应的so，这是较为完美的一种解决方案，同时也会导致包变大。</p>
<p>目前主流的Android设备是armeabi-v7a架构的，然后就是x86和armeabi了。</p>
</blockquote>
</li>
<li><p>因为是无法播放视频，所以查看视频播放框架特性，及是否有机型不支持问题。查到数据如下：<br><a target="_blank" rel="noopener" href="https://www.vitamio.org/docs/Basic/2013/0508/13.html">Vitamio不支持特性列表</a></p>
</li>
</ol>
<p><img src="https://img-blog.csdnimg.cn/20190429175133271.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poZW5nX3dlaWNoYW8=,size_16,color_FFFFFF,t_70" alt="Vitamio不支持特性列表"></p>
<p>其中第1点，第7点，第8点，第14点需要引起注意。</p>
<p>我也花费了大量的时间和精力在这部分内容上，进行了多处包裹异常，以及机型判断，结果并无实质性改变。</p>
<p><strong>科普：</strong></p>
<blockquote>
<p>早期的Android系统几乎只支持ARMv5的CPU架构，现在Android系统支持以下七种不同的CPU架构：ARMv5，ARMv7 (从2010年起)，x86 (从2011年起)，MIPS (从2012年起)，ARMv8，MIPS64和x86_64 (从2014年起)，每一种都关联着一个相应的ABI。</p>
</blockquote>
<h2 id="最终解决"><a href="#最终解决" class="headerlink" title="最终解决"></a>最终解决</h2><p>找到了一部oppo  a53m手机，经测试，重现了客户提到的问题。遂委托同事进行日志查看。</p>
<p>报错信息如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">java. lang. NoSuchMethodError:<span class="function">No virtual method <span class="title">getColor</span><span class="params">(I)</span>I in class Landroid/content/Context</span>; <span class="function">or its <span class="keyword">super</span> <span class="title">classes</span> <span class="params">(declaration of<span class="string">&#x27; android. content. Context&#x27;</span> appears in /system/frameworlk/franework. jar)</span></span></span><br><span class="line"><span class="function">at com sxmh. wt. education. adapter. lesson. RoWatchlessonlisthdapter. <span class="title">onBindViewHolder</span><span class="params">(RviWatchlessonl istAdapter. java:<span class="number">54</span>)</span></span></span><br><span class="line"><span class="function">at com sxh. wt. educationadapter. lesson. RvTatchlessonlistAdapter. <span class="title">onBindviewHolder</span><span class="params">(RvWatchlessonl istidapter. java:<span class="number">23</span>)</span></span></span><br></pre></td></tr></table></figure>

<p>轻易可得：问题出现在设置颜色时，低版本系统无此方法。问题代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">holder.tvTitle.setTextColor(context.getColor(R.color.colorTextRed));</span><br></pre></td></tr></table></figure>

<p><strong>解决办法：</strong></p>
<p>修改为兼容低版本系统的方法。两种任选其一即可，如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 该方法可以解决问题，但是不推荐</span></span><br><span class="line">holder.tvTitle.setTextColor(context.getResources().getColor(R.color.colorTextRed));</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 推荐写法，兼容性更好</span></span><br><span class="line">holder.tvTitle.setTextColor(ContextCompat.getColor(context, R.color.colorTextRed));</span><br></pre></td></tr></table></figure>


<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul>
<li>一句代码就可能弄丢项目，导致公司的业务方向受损，代码开发，应仔细认真。</li>
<li>安卓机型适配问题十分复杂，最好找到同款或者相似机型，能复现问题后再做处理。</li>
<li>平常写代码，应多注意适配问题。本问题即为常见的适配问题，注意书写即可避免。</li>
<li>代码bug原因关联很多，不能死盯一处，处理问题的方向错了，怎么解决也无济于事。</li>
</ul>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E7%BB%99APP%E4%B8%8A%E4%BB%BD%E4%BF%9D%E9%99%A9%EF%BC%9A%E5%AE%89%E5%8D%93%E7%9A%84%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E6%8D%95%E8%8E%B7/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E7%BB%99APP%E4%B8%8A%E4%BB%BD%E4%BF%9D%E9%99%A9%EF%BC%9A%E5%AE%89%E5%8D%93%E7%9A%84%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8%E6%8D%95%E8%8E%B7/" class="post-title-link" itemprop="url">给APP上份保险：安卓的全局异常捕获</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>世界上没有完美的代码，你永远不知道bug什么时候会来……</p>
<blockquote>
<p>/** 不要犹豫了，赶紧给你的代码上份保险吧，不要三四千，不要一两千，只要花10分钟复制下代码就好啦！*/ 算了，这段话假装没有看见吧……</p>
</blockquote>
<h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>对于所有的客户端程序来说，环境的复杂性和不可控性总会导致各种各样意料之外的bug。这点对是国内的安卓开发者来说尤为明显。<br>首先安卓就有多个版本，从2.X到9.X，其次国内厂商的多种定制ROM更加剧了这个复杂程度……<br>总而言之，稳定性测试是保障客户端稳定性的一种手段，收集更多异常信息，复现问题解决问题。</p>
<p>那么当Android客户端发生闪退时，是如何捕获到闪退信息的呢？</p>
<h2 id="Java的异常"><a href="#Java的异常" class="headerlink" title="Java的异常"></a>Java的异常</h2><p>Java语言在设计之初就提供了相对完美的异常处理机制，所有的异常类是从java.lang,Exception类继承的子类，Exception类是Throwable类的子类，除了Exception,Throwable还有一个子类Error，在java中只有Throwable类型的实例才可以被抛出（throw）或者（catch）,它是异常处理机制的基本组成部分。</p>
<p><strong>Exception和Error</strong><br>Exception和Error体现了Java平台设计者对不同异常情况的分类，Exception是程序正常运行中，可以预料的意外情况，可能并且应该被捕获。Error是指在正常情况下，不太可能出现的情况，绝大部分的Error都会导致程序处理非正常的，不可恢复的状态，例如典型的OutOfMemoryError.</p>
<blockquote>
<p>Exception分为可检查异常（checked）和不检查异常（unchecked）异常，可检查异常必须在代码里显式进行捕获处理，这是编译期检查的一部分。不检查异常是所谓的运行时异常，类似NullPointException,ArrayIndexOutOfBoundsException,不会在编译器进行检查。</p>
</blockquote>
<h2 id="扩展知识"><a href="#扩展知识" class="headerlink" title="扩展知识"></a>扩展知识</h2><p>关于异常捕获的基本原则：<br>1.尽量不要捕获类似Exception这样的异常，应该捕获特定的异常。<br>2.不要生吞异常（swallow），因为这样可能会导致非常难以诊断的诡异情况。<br>3.try-catch代码段会产生额外的性能开销，换个角度，它往往会影响JVM对代码进行优化，所以建议仅仅捕获有必要的代码，尽量不要一个大的try包住整段的代码。<br>4.Java每实例化一个Exception，都会对当时的栈进行快照，这是一个非常重的操作，如果很频繁，这个开销就不能被忽略了。</p>
<h2 id="RuntimeException的异常导致的app崩溃，应该如何来捕获？"><a href="#RuntimeException的异常导致的app崩溃，应该如何来捕获？" class="headerlink" title="RuntimeException的异常导致的app崩溃，应该如何来捕获？"></a>RuntimeException的异常导致的app崩溃，应该如何来捕获？</h2><p>Android全局异常捕获Android应用不可避免的发生crash,也就是崩溃，不同厂商的Rom,硬件导致的兼容性问题，都会导致应该异常或者崩溃。<br>接下来看一下Thread类里面的UncaugthExceptionHandler静态接口，ThreadGroup是它的子类，抽象方法uncaughtException(Thread t,Throwable e)。<br>官方文档是这样介绍的，<br>Thread.UncauthExceptionHandler接口，uncaught exception发生，一个线程突然停止，它的处理者（handler）,就会被调用。<br>JVM会通过Thread.getUncauthException()查询线程的UncaughtExceptionHandler,同时唤起处理者的uncaughtException方法，thread和exception作为参数传递，</p>
<p>如果一个线程没有UncaughtExceptionHandker，这时ThreadGroup对象作为该线程的UncaughtExceptionHandler,ThreadGroup没有明确的需求处理异常，它可以把调用传递给default uncauth exception handler.下面通过一个demo演示全局异常捕获</p>
<p>Thread.UncaughtExceptionHandler是一个当线程由于未捕获的异常突然终止而调用处理程序的接口.<br>当线程由于未捕获的异常即将终止时，Java虚拟机将使用它来查询其UncaughtExceptionHandler的线程Thread.getUncaughtExceptionHandler()，并将调用处理程序的 uncaughtException方法，将线程和异常作为参数传递。如果一个线程没有显示它的UncaughtExceptionHandler，那么它的ThreadGroup对象充当它的 UncaughtExceptionHandler。如果ThreadGroup对象没有处理异常的特殊要求，它可以将调用转发到默认的未捕获的异常处理程序。</p>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>因此我们可以自定一个类去实现该接口，该类主要用于收集错误信息和保存错误信息.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 作用:</span></span><br><span class="line"><span class="comment"> * 1.收集错误信息</span></span><br><span class="line"><span class="comment"> * 2.保存错误信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CrashHandler</span> <span class="keyword">implements</span> <span class="title">Thread</span>.<span class="title">UncaughtExceptionHandler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> CrashHandler sInstance = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">private</span> Thread.UncaughtExceptionHandler mDefaultHandler;</span><br><span class="line">    <span class="keyword">private</span> Context mContext;</span><br><span class="line">    <span class="comment">// 保存手机信息和异常信息</span></span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, String&gt; mMessage = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> CrashHandler <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (sInstance == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (CrashHandler.class) &#123;</span><br><span class="line">                <span class="keyword">if</span> (sInstance == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">synchronized</span> (CrashHandler.class) &#123;</span><br><span class="line">                        sInstance = <span class="keyword">new</span> CrashHandler();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sInstance;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">CrashHandler</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 初始化默认异常捕获</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> context context</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">        mContext = context;</span><br><span class="line">        <span class="comment">// 获取默认异常处理器</span></span><br><span class="line">        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();</span><br><span class="line">        <span class="comment">// 将此类设为默认异常处理器</span></span><br><span class="line">        Thread.setDefaultUncaughtExceptionHandler(<span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">uncaughtException</span><span class="params">(Thread t, Throwable e)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!handleException(e)) &#123;</span><br><span class="line">            <span class="comment">// 未经过人为处理,则调用系统默认处理异常,弹出系统强制关闭的对话框</span></span><br><span class="line">            <span class="keyword">if</span> (mDefaultHandler != <span class="keyword">null</span>) &#123;</span><br><span class="line">                mDefaultHandler.uncaughtException(t, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 已经人为处理,系统自己退出</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e1) &#123;</span><br><span class="line">                e1.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            Process.killProcess(Process.myPid());</span><br><span class="line">            System.exit(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 是否人为捕获异常</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e Throwable</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> true:已处理 false:未处理</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">handleException</span><span class="params">(Throwable e)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="keyword">null</span>) &#123;<span class="comment">// 异常是否为空</span></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">new</span> Thread() &#123;<span class="comment">// 在主线程中弹出提示</span></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                Looper.prepare();</span><br><span class="line">                Toast.makeText(mContext, <span class="string">&quot;捕获到异常&quot;</span>, Toast.LENGTH_SHORT).show();</span><br><span class="line">                Looper.loop();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;.start();</span><br><span class="line">        collectErrorMessages();</span><br><span class="line">        saveErrorMessages(e);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 1.收集错误信息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">collectErrorMessages</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        PackageManager pm = mContext.getPackageManager();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);</span><br><span class="line">            <span class="keyword">if</span> (pi != <span class="keyword">null</span>) &#123;</span><br><span class="line">                String versionName = TextUtils.isEmpty(pi.versionName) ? <span class="string">&quot;null&quot;</span> : pi.versionName;</span><br><span class="line">                String versionCode = <span class="string">&quot;&quot;</span> + pi.versionCode;</span><br><span class="line">                mMessage.put(<span class="string">&quot;versionName&quot;</span>, versionName);</span><br><span class="line">                mMessage.put(<span class="string">&quot;versionCode&quot;</span>, versionCode);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 通过反射拿到错误信息</span></span><br><span class="line">            Field[] fields = Build.class.getFields();</span><br><span class="line">            <span class="keyword">if</span> (fields != <span class="keyword">null</span> &amp;&amp; fields.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">for</span> (Field field : fields) &#123;</span><br><span class="line">                    field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        mMessage.put(field.getName(), field.get(<span class="keyword">null</span>).toString());</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (PackageManager.NameNotFoundException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 2.保存错误信息</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e Throwable</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">saveErrorMessages</span><span class="params">(Throwable e)</span> </span>&#123;</span><br><span class="line">        StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;String, String&gt; entry : mMessage.entrySet()) &#123;</span><br><span class="line">            String key = entry.getKey();</span><br><span class="line">            String value = entry.getValue();</span><br><span class="line">            sb.append(key).append(<span class="string">&quot;=&quot;</span>).append(value).append(<span class="string">&quot;\n&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        Writer writer = <span class="keyword">new</span> StringWriter();</span><br><span class="line">        PrintWriter pw = <span class="keyword">new</span> PrintWriter(writer);</span><br><span class="line">        e.printStackTrace(pw);</span><br><span class="line">        Throwable cause = e.getCause();</span><br><span class="line">        <span class="comment">// 循环取出Cause</span></span><br><span class="line">        <span class="keyword">while</span> (cause != <span class="keyword">null</span>) &#123;</span><br><span class="line">            cause.printStackTrace(pw);</span><br><span class="line">            cause = e.getCause();</span><br><span class="line">        &#125;</span><br><span class="line">        pw.close();</span><br><span class="line">        String result = writer.toString();</span><br><span class="line">        sb.append(result);</span><br><span class="line">        String time = <span class="keyword">new</span> SimpleDateFormat(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>, Locale.CHINA).format(<span class="keyword">new</span> Date());</span><br><span class="line">        String fileName = <span class="string">&quot;crash-&quot;</span> + time + <span class="string">&quot;-&quot;</span> + System.currentTimeMillis() + <span class="string">&quot;.log&quot;</span>;</span><br><span class="line">        <span class="comment">// 有无SD卡</span></span><br><span class="line">        <span class="keyword">if</span> (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) &#123;</span><br><span class="line">            String path = Environment.getExternalStorageDirectory().getPath() + <span class="string">&quot;crash/&quot;</span>;</span><br><span class="line">            File dir = <span class="keyword">new</span> File(path);</span><br><span class="line">            <span class="keyword">if</span> (!dir.exists()) dir.mkdirs();</span><br><span class="line">            FileOutputStream fos = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                fos = <span class="keyword">new</span> FileOutputStream(path + fileName);</span><br><span class="line">                fos.write(sb.toString().getBytes());</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e1) &#123;</span><br><span class="line">                e1.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (fos != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        fos.close();</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (IOException e1) &#123;</span><br><span class="line">                        e1.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>在Application中调用</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CrashHandler.getInstance().init(<span class="keyword">this</span>);</span><br></pre></td></tr></table></figure>
<p>逻辑代码中，随便new RuntimeException(“XXX 自定义异常”)，或者写一个固定这时uncaughtHandlerException就可以捕获程序的异常了。</p>
<p>本文参考：<br><a target="_blank" rel="noopener" href="https://blog.csdn.net/klxh2009/article/details/77480108">https://blog.csdn.net/klxh2009/article/details/77480108</a><br><a target="_blank" rel="noopener" href="https://www.cnblogs.com/sesexxoo/p/6190507.html">https://www.cnblogs.com/sesexxoo/p/6190507.html</a><br><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/84eba8efa45e">https://www.jianshu.com/p/84eba8efa45e</a></p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E7%BA%B3%E5%B0%BC%EF%BC%9FCollections%E5%AF%B9%E6%97%B6%E9%97%B4%E6%88%B3%E6%8E%92%E5%BA%8F%E5%87%BA%E9%94%99%E4%BA%86%EF%BC%9F%EF%BC%9F/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E7%BA%B3%E5%B0%BC%EF%BC%9FCollections%E5%AF%B9%E6%97%B6%E9%97%B4%E6%88%B3%E6%8E%92%E5%BA%8F%E5%87%BA%E9%94%99%E4%BA%86%EF%BC%9F%EF%BC%9F/" class="post-title-link" itemprop="url">纳尼？Collections对时间戳排序出错了？？</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <blockquote>
<p>记录一次排序犯的bug，说到底还是考虑不全……</p>
</blockquote>
<h1 id="问题现象"><a href="#问题现象" class="headerlink" title="问题现象"></a>问题现象</h1><p>今天突然发现项目中的一个列表排序出现了错乱，顿时很慌张，原来测试过的啊，没问题的呀，这是怎么回事，赶紧定位问题，现象如下：<br>1-9条数据排序正常，10条以后数据排序也对。但是这两块顺序不对。正常应该先展示10条以后的数据，最后再展示1-9条数据。（后来得知，这个现象属于偶然~~和数据有关系）</p>
<p>列表中部分有序，但是整体排序不对……</p>
<h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>恩？这是怎么回事？<br>列表采用的分页加载，莫非是 <strong>分页请求</strong> 数据的原因？</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">￥@#&amp;……*……@￥</span><br></pre></td></tr></table></figure>
<p>一顿分析后，得出结论，分页请求没问题，不是这个原因。</p>
<p>列表用的是RecycleView，莫非是 常见的 <strong>列表复用</strong> 的原因？</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">￥@#&amp;……*……@￥</span><br></pre></td></tr></table></figure>

<p>一顿分析后，得出结论，不是没问题。</p>
<p>那会不会是排序本身的问题呢？ </p>
<h2 id="那看一下排序的代码"><a href="#那看一下排序的代码" class="headerlink" title="那看一下排序的代码"></a>那看一下排序的代码</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 网络请求获取数据，封装再beanList中</span></span><br><span class="line">...</span><br><span class="line"><span class="comment">// 对beanList进行排序</span></span><br><span class="line">Collections.sort(beanList, <span class="keyword">new</span> Comparator&lt;Bean&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">compare</span><span class="params">(Bean bean1, Bean bean2)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//        降序写法</span></span><br><span class="line">        <span class="keyword">return</span> (<span class="keyword">int</span>) (bean2.getBuyTime() - bean1.getBuyTime());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>其实大佬一眼就能看出来原因了，奈何我这心大，看了好几遍，还总觉得没问题。<br>就这么几行代码咋能出错呢？还纠结了半天，最后才聚焦到了 int类型 上面……<br>是不是这个数值超过了int的最大值，所以计算错误了呢？</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">int的取值范围为： -2^31——2^31-1，即</span><br><span class="line">-21 4748 3648——21 4748 3647</span><br><span class="line"></span><br><span class="line">2147483647毫秒 ---&gt; 2147483 秒 ---&gt; 35791 分钟 ---&gt; 596 小时 </span><br><span class="line">≈ 25天</span><br><span class="line">2147483647秒 ---&gt; 35791406 分钟 ---&gt; 596523小时</span><br><span class="line">≈ 24855天 ≈ 68年</span><br></pre></td></tr></table></figure>
<p>也就是说，如果是毫秒作为单位的话，<strong>int类型最多表示25天时间。<br>如果时间超过这个范围，那么就会数据错误！</strong><br>如果是秒作为单位的话，int类型大约可以表示68年时间。<br>我这里之所以出错，是因为时间超过了这个范围，所以导致排序的时候，错误地判断了大小关系。</p>
<h1 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h1><p>至此，定位原因。解决方法也就简单了：<br>（尴尬，犯了基础的错误……）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Collections.sort(beanList, <span class="keyword">new</span> Comparator&lt;Bean&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">compare</span><span class="params">(Bean bean1, Bean bean2)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//        降序写法</span></span><br><span class="line">		<span class="comment">// 原先采用的多次比较的方法，先判断是否是同年月，再return时间差，费时费力。</span></span><br><span class="line">		<span class="comment">// 感谢评论区大佬 【常威追打来福】 指点，比较方法已经修改如下：</span></span><br><span class="line">		<span class="keyword">if</span>(bean2.getBuyTime() - bean1.getBuyTime() &gt; <span class="number">0</span>)&#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">		&#125;<span class="keyword">else</span> <span class="keyword">if</span>(bean2.getBuyTime() - bean1.getBuyTime() &lt; <span class="number">0</span>)&#123;</span><br><span class="line">			<span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">		&#125;<span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">		&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>程序开发是个细致的活，一定要细心呀！不要因为马虎，在基础的知识上面翻了车。<br>在此，再次回顾下基础类型的基础知识，切记勿再犯相关错误！</p>
<h1 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h1><p>在JAVA中一共有八种基本数据类型，他们分别是<br>byte、short、int、long、float、double、char、boolean </p>
<p>byte的取值范围为-128<del>127，占用1个字节（-2的7次方到2的7次方-1）<br>short的取值范围为-32768</del>32767，占用2个字节（-2的15次方到2的15次方-1）<br>int的取值范围为（-2147483648<del>2147483647），占用4个字节（-2的31次方到2的31次方-1）<br>long的取值范围为（-9223372036854774808</del>9223372036854774807），占用8个字节（-2的63次方到2的63次方-1）</p>
<p>float 3.402823e+38 ~ 1.401298e-45（e+38表示是乘以10的38次方，同样，e-45表示乘以10的负45次方）占用4个字节<br>double 1.797693e+308~ 4.9000000e-324 占用8个字节 </p>
<blockquote>
<p>float和double是表示浮点型的数据类型，他们之间的区别在于他们的精确度不同<br>double型比float型存储范围更大，精度更高，所以通常的浮点型的数据在不声明的情况下都是double型的，如果要表示一个数据是float型的，可以在数据后面加上“F”。<br>浮点型的数据是不能完全精确的，所以有的时候在计算的时候可能会在小数点最后几位出现浮动，这是正常的。 </p>
</blockquote>
<p>boolean型（布尔型）<br>这个类型只有两个值，true和false（真和非真） </p>
<p>char型（文本型）<br>用于存放字符的数据类型，占用2个字节</p>
<blockquote>
<p>采用unicode编码，它的前128字节编码与ASCII兼容，字符的存储范围在\u0000~\uFFFF</p>
</blockquote>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E6%9F%A5%E7%9C%8BAndroid%E8%AE%BE%E5%A4%87%E7%9A%84CPU%E6%9E%B6%E6%9E%84%E4%BF%A1%E6%81%AF/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E6%9F%A5%E7%9C%8BAndroid%E8%AE%BE%E5%A4%87%E7%9A%84CPU%E6%9E%B6%E6%9E%84%E4%BF%A1%E6%81%AF/" class="post-title-link" itemprop="url">查看Android设备的CPU架构信息</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <blockquote>
<p>查看Android设备的CPU架构信息<br>     db shell cat  /proc/cpuinfo</p>
</blockquote>
<p>  最近在做一个项目中使用了vitamio视频播放框架，框架不支持部分cpu，所以需要在app开发过程中去判断cpu的arm架构，比如说是armeabi-v7a，或是arm64-v8a。</p>
<pre><code>其实，在adb shell命令下面，可以通过getprop的方式，获取到一些信息，比如：

rk3399_urbetter:/ # getprop|grep arm
[dalvik.vm.isa.arm.features]: [default]
[dalvik.vm.isa.arm.variant]: [cortex-a53.a57]
[dalvik.vm.isa.arm64.features]: [default]
[dalvik.vm.isa.arm64.variant]: [cortex-a53]
[persist.sys.alarm.fixed]: [300000]
[ro.config.alarm_alert]: [Alarm_Classic.ogg]
[ro.product.cpu.abi]: [arm64-v8a]
[ro.product.cpu.abilist]: [arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [arm64-v8a]
rk3399_urbetter:/ #
</code></pre>
<p>   可以看到，现在cpu的架构是支持arm64-v8a。<br>科普一下：</p>
<blockquote>
<p>processor: 0/1 表示第几个核。<br>BogoMIPS：伪MIPS，用于测量CPU速度，详细分析可见：<br><a target="_blank" rel="noopener" href="https://www.linux-mips.org/wiki?title=bogomips&amp;oldid=6231">https://www.linux-mips.org/wiki?title=bogomips&amp;oldid=6231</a><br><a target="_blank" rel="noopener" href="https://blog.csdn.net/ly890700/article/details/52294769">https://blog.csdn.net/ly890700/article/details/52294769</a><br>Features：表示当前CPU所支持的特性，比如neon，vfp等。<br>CPU architecture：7表示arm-v7，8表示arm-v8<br>AArch64是ARMv8 架构的一种执行状态。<br>为了更广泛地向企业领域推进，需要引入 64 位构架。同时也需要在 ARMv8 架构中引入新的 AArch64 执行状态。AArch64 不是一个单纯的 32 位 ARM 构架扩展，而是 ARMv8 内全新的构架，完全使用全新的 A64 指令集。这些都源自于多年对现代构架设计的深入研究。更重要的是， AArch64 作为一个分离出的执行状态，意味着一些未来的处理器可能不支持旧的 AArch32 执行状态。 虽然最初的 64 位 ARM 处理器将会完全向后兼容，但我们大胆且前瞻性地将 AArch64 作为在 ARMv8 处理器中唯一的执行状态。我们在这些系统中将不支持 32 位执行状态， 这将使许多有益的实现得到权衡，如默认情况下，使用一个较大的 64K 大小的页面，并会使得纯净的 64 位 ARM 服务器系统不受遗留代码的影响。立即进行这种划分是很重要的，因为有可能在未来几年内将出现仅支持 64 位的服务器系统。没有必要在新的 64 位架构中去实现一个完整的 32 位流水线，这将会提高未来 ARM 服务器系统的能效。这样回想起来， AArch64 作为在 Fedora ARM 项目中被支持的 ARM 构架是一个很自然的过程： armv5tel、armv7hl、aarch64。新的架构被命名为：aarch64，这同 ARM 自己选择的主线命名方式保持一致，同时也考虑到了 ARM 架构名与 ARM 商标分开的期望。<br>ARMv8-A将 64 位架构支持引入 ARM 架构中，其中包括：<br>64 位通用寄存器、SP（堆栈指针）和 PC（程序计数器）<br>64 位数据处理和扩展的虚拟寻址</p>
</blockquote>
<p>两种主要执行状态：<br>AArch64 - 64 位执行状态，包括该状态的异常模型、内存模型、程序员模型和指令集支持<br>AArch32 — 32 位执行状态，包括该状态的异常模型、内存模型、程序员模型和指令集支持<br>这些执行状态支持三个主要指令集<br>A32（或 ARM）：32 位固定长度指令集，通过不同架构变体增强部分 32 位架构执行环境现在称为 AArch32。<br>T32 (Thumb) 是以 16 位固定长度指令集的形式引入的，随后在引入 Thumb-2 技术时增强为 16 位和 32 位混合长度指令集。部分 32 位架构执行环境现在称为 AArch32。<br>A64：提供与 ARM 和 Thumb 指令集类似功能的 32 位固定长度指令集。随 ARMv8-A 一起引入，它是一种 AArch64 指令集。<br>ARM ISA 不断改进，以满足前沿应用程序开发人员日益增长的要求，同时保留了必要的向后兼容性，以保护软件开发投资。在 ARMv8-A 中，对 A32 和 T32 进行了一些增补，以保持与 A64 指令集一致。</p>
<p>   那么，在app的开发过程中，是怎么样获取到这个值呢。</p>
<p>分成两个层次来讨论：</p>
<p> 1） native层</p>
<pre><code> 在native层，可以通过property_get()函数来实现，比如：

char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, &quot;0&quot;);
</code></pre>
<p>2） java层</p>
<p>这次遇到的问题就是需要在java层去做这个事情。网络上面有介绍可以使用System.getproperty()来达到这个目标，不过Java的System.getProperty得到null，暂时还没有解决。但是可以寻求折中的解决方案，就是在java层执行shell 命令，直接通过getprop的shell命令来获取到结果。</p>
<pre><code>cpu_abi = mCMD.execCmd(&quot;getprop ro.product.cpu.abi&quot;);
        public static String execCmd(String cmd) &#123;
                DataOutputStream dos = null;
                String result = &quot;&quot;;
                String lastline = &quot; &quot;;
                try &#123;
                    Process process = Runtime.getRuntime().exec(cmd);// 经过Root处理的android系统即有su命令
                    //get the err line
                    InputStream stderr = process.getErrorStream();
                    InputStreamReader isrerr = new InputStreamReader(stderr);
                    BufferedReader brerr = new BufferedReader(isrerr);
         
                    //get the output line
                    InputStream outs = process.getInputStream();
                    InputStreamReader isrout = new InputStreamReader(outs);
                    BufferedReader brout = new BufferedReader(isrout);
                    String errline = null;
         
         
                    // get the whole error message
                    String  line = &quot;&quot;;
         
                    while ( (line = brerr.readLine()) != null)
                    &#123;
                        result += line;
                        result += &quot;/n&quot;;
                    &#125;
         
                    if( result != &quot;&quot; )
                    &#123;
                        // put the result string on the screen
                        Log.i(TAG,&quot; the str error message &quot; + result.toString());
                    &#125;
         
                    // get the whole standard output string
                    while ( (line = brout.readLine()) != null)
                    &#123;
                        lastline = line;
                        result += line;
                        result += &quot;/n&quot;;
                    &#125;
                    if( result != &quot;&quot; )
                    &#123;
                        // put the result string on the screen
                        Log.i(TAG,&quot; the standard output message &quot; + lastline.toString());
                    &#125;
                &#125;catch(Throwable t)
                &#123;
                    t.printStackTrace();
                &#125;
                return lastline.toString();
            &#125;

 
</code></pre>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E6%8C%87%E7%BA%B9%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91-FPW(M)%E6%8C%87%E7%BA%B9%E7%B3%BB%E5%88%97%E4%BA%A7%E5%93%81/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E6%8C%87%E7%BA%B9%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91-FPW(M)%E6%8C%87%E7%BA%B9%E7%B3%BB%E5%88%97%E4%BA%A7%E5%93%81/" class="post-title-link" itemprop="url">指纹模块开发-FPW(M)指纹系列产品</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h1 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h1><blockquote>
<p>根据杭州蚁创科技有限公司《FPW(M)指纹系列产品开发手册》编写， 仅适用对应硬件，留作备忘。</p>
</blockquote>
<p>在android6.0之后谷歌对指纹识别进行了官方支持，目前指纹领域无论从产品角度还是技术角度都已经趋于成熟，开发过程中遇到很多的问题，在此先按下不表。如果在没有指纹模块的安卓系统上进行指纹开发，就需要相应硬件的支持。于是采用了杭州蚁创科技有限公司的硬件。本篇博文内容也仅适应对应硬件。在这里抛砖引玉，希望能让大家开发时少走弯路。</p>
<h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h1><h2 id="1-添加权限："><a href="#1-添加权限：" class="headerlink" title="1. 添加权限："></a>1. 添加权限：</h2><p>指纹开发，硬件需要通过usb连接到安卓设备上。在此也需要将usb接口设置成host模式，不然的话无法进行识别。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">uses-feature</span> <span class="attr">android:name</span>=<span class="string">&quot;android.hardware.usb.host&quot;</span>  <span class="attr">android:required</span>=<span class="string">&quot;true&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.READ_EXTERNAL_STORAGE&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure>
<p>如果是安卓6.0以及以上系统，还需要动态获取权限。</p>
<h2 id="2-导入jar包和so文件"><a href="#2-导入jar包和so文件" class="headerlink" title="2. 导入jar包和so文件"></a>2. 导入jar包和so文件</h2><h2 id="3-愉快的使用"><a href="#3-愉快的使用" class="headerlink" title="3. 愉快的使用"></a>3. 愉快的使用</h2><p> 方法</p>
<ul>
<li>初始化设备<br>public int Init();<br>返回值参考常量</li>
<li> 关闭设备<br>public int Close();<br>返回值参考常量</li>
<li>SDK 版本<br>public String GetVersion ();</li>
<li>获取图像大小<br>public int GetImageSize(int []pnWidth, int []pnHeight);<br>pnWidth 图像宽度<br>pnHeight 图像高度<br>返回值参考常量</li>
<li>检测手指<br>public int DetectFinger(int[]isPressed)<br>isPressed =0 手指抬起 =1 手指按下<br>返回值参考常量</li>
<li>上传原始图像<br>public int GetImageRawData(byte[]pRawData);<br>pRawData 图像大小 pnWidth*pnHeight<br>返回值参考常量</li>
<li>获取图像质量<br>public int GetImageScore(byte[] pRawData,int nWidth, int nHeight);<br>pRawData 图像大小 pnWidth*pnHeight<br>pnScore 大于 IMAGE_SCORE 合格<br>返回值参考常量</li>
<li>将原始图像转换成 bmp<br>public Bitmap ImageRawDataToBmp(byte[] pRawData, int nWidth, int<br>nHeight);</li>
<li>特征提取<br>public int Extract(byte[] pRawData,int nWidth, int nHeight,byte[]<br>pfeature);<br>pRawData 图像大小 nWidth * nHeight<br>pfeature 特征大小 FEATURE_LENTH<br>返回值参考常量</li>
<li>将三个特征转换成模板<br>public int Enroll(byte[] pfeature1,byte[] pfeature2,byte[]<br>pfeature3,byte[] pTemplate);<br>pfeature1, pfeature2, pfeature3, pTemplate 大小 FEATURE_LENTH<br>返回值参考常量<br>一般将模板保存在后台数据库， 便于后期识别验证。FPW(M)指纹系列产品开发手册<br>杭州蚁创科技有限公司</li>
<li>指纹匹配<br>public int Match(byte[] pfeature,byte[] pTemplate,int iLevel);<br>两个特征相互匹配<br>MATCH_LOW_LEVEL&lt;=iLevel&lt;= MATCH_HIGH_LEVEL,建议 3<br>返回值参考常量</li>
<li>搜索<br>public int Search(byte[] pfeature1,byte[] pTemplate_n,int nTemplate);<br>特征 pfeature1 大小 FEATURE_LENTH<br>模 板 数 据 pTemplate_n 按 照 FTP_MAX_SIZE 的 长 度 累 计 长 度 为 nTemplate</li>
</ul>
<p>*FEATURE_LENTH<br>模板个数 nTemplate<br>返回成功的索引 pIndex<br>最大个数 10000， 如果超过该限制， 与相关人员联系。</p>
<h1 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h1><blockquote>
<p>支付宝，腾讯等大厂都制定了自己的协议，标准直接和设备厂商合作，而应用方只有微信和支付宝自己。支付宝指纹支付标准是 IFAA ，腾讯的指纹支付标准是 SOTER，也就是说没有其他应用方会使用这个标准。所以很看应用方和设备厂商的协商程度。现在 IFAA 没有开源，只有 SOTER 是开源的了<br>如果接入，我们能省去兼容性测试的工作量，而且有些 6.0 以下的机型 SOTER 也支持。每个指纹将会有唯一 ID，也就是说，可以能把账号和指纹绑定起来，更加安全。<br>不过需要注意的是 SOTER 没支持华为等机型，而且以后机型的扩展受第三方支持的限制。</p>
</blockquote>
<h1 id="指纹开发常见问题"><a href="#指纹开发常见问题" class="headerlink" title="指纹开发常见问题"></a>指纹开发常见问题</h1><p>兼容问题</p>
<ol>
<li>明明符合条件，isHardwareDetected() 返回 false？<br>表现机型：MI 5s、vivo X9<br>在同一机型上调用 FingerprintManagerCompat 的 isHardwareDetected() 和 hasEnrolledFingerprints() 时候，返回的都是 false,但是调用 FingerprintManager 的 isHardwareDetected()<br>和 hasEnrolledFingerprints() 时，却是返回 true。<br>解决：是否符合指纹条件可以多加一层判断。</li>
<li>Letv X500 Android 6.0,API23   不按正常的套路回调<br>onAuthenticationError 和 onAuthenticationFailed，理论上应该是识别失败的情况，但是该机型点击取消指纹识别也会先回调一次Error，如果遇到这种情况，只能根据具体项目环境中去进行规避适配了。</li>
<li>魅族上遇到的坑<br>onAuthenticationHelp 回调不按套路出牌，正常官网文档解释，这个方法的回调时机是在指纹认证期间发生可恢复性的错误时回调。结果在魅族上，启动指纹识别认证的时候就会回调这个方法，里面传递回来的信息提示是“等待按下手指”，也就是说，它的 onAuthenticationHelp 回调跟官网时机不一样，而且方法的作用也变了，它在正常的情况回调了 onAuthenticationHelp。<br>解决：不影响验证流程，无需解决</li>
<li>小米 锁屏和切后台生命周期不一致<br>产品需求：用户锁屏或切到后台时（onStop）自动停止指纹验证，回到界面时（onResume）自动调起验证。<br>所以我在指纹回调方法中加入了标志位 isInAuth。onStop时保存 isInAuth，onResume时 isInAuth == true 则自动调起验证。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAuthenticationSucceeded</span><span class="params">(FingerprintManagerCompat.AuthenticationResult result)</span> </span>&#123;</span><br><span class="line">    isInAuth = <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAuthenticationError</span><span class="params">(<span class="keyword">int</span> errMsgId, CharSequence errString)</span> </span>&#123;</span><br><span class="line">    isInAuth = <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAuthenticationFailed</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    isInAuth = <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAuthenticationHelp</span><span class="params">(<span class="keyword">int</span> helpMsgId, CharSequence helpString)</span> </span>&#123;</span><br><span class="line">    isInAuth = <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<p>然而小米6、米mix2 锁屏时的生命周期是 onAuthenticationError -&gt; onStop；切到后台是 onStop -&gt; onAuthenticationError。导致不同流程下拿到 isInAuth 标志位不一致，无法自动调起验证。<br>解决：界面指纹按钮可以手动调起验证，无需兼容处理。<br>小米5生命周期同上，但是无论是自动还是手动调起验证，马上就回调了 onAuthenticationError，也就是说 MI5 从后台切回来后，指纹验证流程中断。<br>解决：用一个栈来存储调用方法顺序，如果验证方法调起，马上就回调 onAuthenticationError 方法，则判定是属于兼容问题，按验证失败来解决。<br>5. 密钥解密失败<br>三星SM-A9100 、Nexus 6P密钥解密失败<br>解决：暂无法解决</p>
<p>其他兼容解决方案：</p>
<p>三星passSdk（不过从2018下半年开始，Pass SDK 将不再提供 DEVICE_FINGERPRINT_UNIQUE_ID 。也就是不再为每个已注册的指纹提供索引了。因此将无法通过 SDK 区分使用哪个指纹来验证用户。）</p>
<p>魅族 flyme开发平台提供了指纹验证官方api</p>
<p>非兼容问题</p>
<ol>
<li><p>新注册指纹密钥解密失败<br>系统中注册了一个新的指纹的情况下，即使指纹在系统指纹列表里，验证也不通过。<br>解决：删除了当前无效的key，然后根据参数再次生成密钥。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line">       <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAuthenticationSucceeded</span><span class="params">(FingerprintManagerCompat.AuthenticationResult result)</span> </span>&#123;      </span><br><span class="line">       <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * doFinal方法会检查结果是不是会拦截或者篡改过，</span></span><br><span class="line"><span class="comment">        * 如果是的话会抛出一个异常，异常的时候都将认证当做是失败来处理</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           result.getCryptoObject().getCipher().doFinal();</span><br><span class="line">           mCustomCallback.onAuthenticationSucceeded(<span class="keyword">true</span>);</span><br><span class="line">       &#125; <span class="keyword">catch</span> (IllegalBlockSizeException e) &#123;</span><br><span class="line">           <span class="comment">//如果是新录入的指纹，会抛出该异常，需要重新生成密钥对重新验证，这里加个次数限制，避免进入验证异常-&gt;重新验证-&gt;又验证异常的死循环</span></span><br><span class="line">           <span class="keyword">if</span> (happenCount == <span class="number">0</span>) &#123;</span><br><span class="line">               beginAuthenticate();</span><br><span class="line">               happenCount++;</span><br><span class="line">               <span class="keyword">return</span>;</span><br><span class="line">           &#125;</span><br><span class="line">           mCustomCallback.onAuthenticationSucceeded(<span class="keyword">false</span>);</span><br><span class="line">       &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">           mCustomCallback.onAuthenticationSucceeded(<span class="keyword">false</span>);</span><br><span class="line">       &#125;</span><br><span class="line">      ...</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure></li>
<li><p>设备已有指纹，生成密钥却异常提示没有指纹<br>非复现，和设备无关，怀疑是谷歌 API 的坑。<br>java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use</p>
</li>
</ol>
<p>解决：暂时只想到针对这个特定异常，直接使用无密钥验证，有一定的安全风险。</p>
<h1 id="文章推荐"><a href="#文章推荐" class="headerlink" title="文章推荐"></a>文章推荐</h1><ul>
<li>Android开发学习之路-指纹识别api  <a target="_blank" rel="noopener" href="https://www.cnblogs.com/Fndroid/p/5204986.html">https://www.cnblogs.com/Fndroid/p/5204986.html</a></li>
<li>Android指纹识别深入浅出分析到实战（6.0以下系统适配方案） <a target="_blank" rel="noopener" href="https://www.cnblogs.com/popfisher/p/6063835.html">https://www.cnblogs.com/popfisher/p/6063835.html</a></li>
<li>如何在复杂业务场景中优雅实现Android指纹验证？ <a target="_blank" rel="noopener" href="https://www.jianshu.com/p/ed880f35f97f">https://www.jianshu.com/p/ed880f35f97f</a></li>
<li>Android指纹识别，看这一篇就够了 <a target="_blank" rel="noopener" href="https://blog.csdn.net/love_xiaozhao/article/details/81316145">https://blog.csdn.net/love_xiaozhao/article/details/81316145</a></li>
</ul>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E6%89%8B%E6%9C%BA%E8%AE%BF%E9%97%AE%E7%94%B5%E8%84%91%E4%B8%AD%E9%83%A8%E7%BD%B2%E7%9A%84tomcat%E5%BA%94%E7%94%A8/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E6%89%8B%E6%9C%BA%E8%AE%BF%E9%97%AE%E7%94%B5%E8%84%91%E4%B8%AD%E9%83%A8%E7%BD%B2%E7%9A%84tomcat%E5%BA%94%E7%94%A8/" class="post-title-link" itemprop="url">手机访问电脑中部署的tomcat应用</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>﻿## <strong>操作步骤：</strong></p>
<p>1.前提：电脑，手机都连接到同一局域网内。(不必非要连接外网)。</p>
<p><strong>2.需要关闭防火墙。需要关闭防火墙。需要关闭防火墙。</strong></p>
<p>控制面板–&gt;系统和安全–&gt;Windows防火墙–&gt;启用或关闭Windows防火墙–&gt;关闭<br><img src="http://img.blog.csdn.net/20170509191935908" alt="关闭电脑防火墙"></p>
<p>3.启动电脑用的tomcat服务应用。</p>
<p>4.查看电脑的ip地址，cmd–&gt;ipconfig–&gt;无线局域网适配器 WLAN –&gt;IPv4地址<br><img src="http://img.blog.csdn.net/20170509190126993" alt="查看ip地址"><br>这里可以看到我的电脑ip地址为：192.168.1.112。</p>
<p>5.手机浏览器输入地址：<a target="_blank" rel="noopener" href="http://192.168.1.112:8080/%EF%BC%8C">http://192.168.1.112:8080/，</a> 即可访问tomcat主页。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%A6%E4%BD%A0%E6%92%B8%E4%B8%80%E4%B8%AA%E6%A0%A1%E5%9B%ADAPP%EF%BC%88%E5%9B%9B%EF%BC%89%EF%BC%9AAPP%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1%E5%8F%8A%E4%B8%BB%E9%A1%B5%E9%9D%A2%E6%A1%86%E6%9E%B6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%A6%E4%BD%A0%E6%92%B8%E4%B8%80%E4%B8%AA%E6%A0%A1%E5%9B%ADAPP%EF%BC%88%E5%9B%9B%EF%BC%89%EF%BC%9AAPP%E5%8A%9F%E8%83%BD%E8%AE%BE%E8%AE%A1%E5%8F%8A%E4%B8%BB%E9%A1%B5%E9%9D%A2%E6%A1%86%E6%9E%B6/" class="post-title-link" itemprop="url">手把手带你撸一个校园APP（四）：APP框架及功能设计</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <blockquote>
<p>中国加油！武汉加油！</p>
</blockquote>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>开发一个APP，首先应该明确APP想要的功能。<br>本项目基于Bmob进行开发，很多地方需要Bmob相关知识。大家如果没了解过的话，可先自行查看 <a target="_blank" rel="noopener" href="http://doc.bmob.cn/data/android/develop_doc/">Bmob开发文档</a> 。</p>
<p>系列文章：<br><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/80787263">手把手带你撸一个校园APP（一）：项目简介</a><br><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/81456617">手把手带你撸一个校园APP（二）：应用启动和欢迎页面</a><br><a target="_blank" rel="noopener" href="https://blog.csdn.net/zheng_weichao/article/details/81837608">手把手带你撸一个校园APP（三）：用户模块（登录注册等）</a></p>
<h2 id="功能设计"><a href="#功能设计" class="headerlink" title="功能设计"></a>功能设计</h2><p><strong>“科师有约” 校园APP</strong> 设计的定位是 “校园信息聚合平台” ，那就要最大程度上利用学校现有的线上资源，比如学校官网、线上图书馆、微信公众号、微博等信息，并且聚合到我们的APP中，方便学生用户去使用。</p>
<h3 id="功能需求点整理"><a href="#功能需求点整理" class="headerlink" title="功能需求点整理"></a>功能需求点整理</h3><p><strong>通用APP功能</strong>：<br>用户信息，检查更新，意见反馈，关于我们</p>
<p><strong>校园APP功能</strong>：<br>身为校园APP，功能设计就应该从校园生活的方方面面去入手。那么可以总结出来功能点如下：</p>
<p><strong>学习方面</strong>：课程表，掌上图书馆，校历<br><strong>生活方面</strong>：失物招领，二手交易，食堂投诉，后勤保修，宿舍管理，学校建议，校园通讯录等<br><strong>信息方面</strong>：学校官网，通知公告，校园新鲜事，自媒体文章等<br><strong>校园文化方面</strong>：表白墙，图说校园，社团组织</p>
<p>有了基本的功能点，我们就可以对功能点进行下归类安放。</p>
<h3 id="功能归类"><a href="#功能归类" class="headerlink" title="功能归类"></a>功能归类</h3><p>功能点已经总结如上，但是各个功能的使用频次不尽相同。功能归类时，考虑如下：</p>
<ol>
<li>APP主要作用为信息聚合，所以应方便进行各种信息资讯的阅读。因此通知公告，校园新鲜事，自媒体文章等可归类在一起；</li>
<li>食堂投诉，后勤保修，宿舍管理，学校建议等都为学生保障自我权益使用，属于低频次需求，且都需要向学校相关组织反馈信息。可以归类为 “权益保障”；</li>
<li>课程表，掌上图书馆，校历，失物招领，二手交易等属于较高频次的需求，可以放置在首页方便使用；</li>
<li>用户信息，检查更新，意见反馈等通用APP的功能点可以放在一起。</li>
</ol>
<p>因此，APP设计为 底部导航栏 + 页面 形式。<br>共分为“首页”，“新闻”，“我的”三栏。<br><strong>首页</strong>：放置使用频次较高的功能点<br><strong>新闻</strong>：展示新闻列表<br><strong>我的</strong>：放置使用频次较低的功能点以及APP通用功能</p>
<h2 id="主页面"><a href="#主页面" class="headerlink" title="主页面"></a>主页面</h2><p>一般来说，类似的主页面可以采用 RadioGroup + ViewPager +Fragment 来实现。然后给RadioGroup 以及 Viewpager 设置监听事件来制造关联。本项目如此实现也没有问题。</p>
<p>为了提升开发效率，也避免重复创建轮子。项目使用了BottomTabView，在此向作者表示感谢。文章信息如下，大家有兴趣可自行浏览：</p>
<blockquote>
<p>GitHub：<a target="_blank" rel="noopener" href="https://github.com/ifmvo/BottomTabView">BottomTabView</a><br>文章：<a target="_blank" rel="noopener" href="https://blog.ifmvo.cn/2017/05/03/package-bottomtabbaseactivity">Android 快速开发（二），封装一个 BottomTabBaseActivity</a><br>作者：陈序员   <a target="_blank" rel="noopener" href="https://blog.ifmvo.cn/">https://blog.ifmvo.cn</a><br>（但是原文链接访问不了了，下面会讲解下大概的思路逻辑。想了解原理详情的话可以看别人在CSDN转载的文章） <a target="_blank" rel="noopener" href="https://blog.csdn.net/dianziagen/article/details/72478927">https://blog.csdn.net/dianziagen/article/details/72478927</a></p>
</blockquote>
<h3 id="思路逻辑"><a href="#思路逻辑" class="headerlink" title="思路逻辑"></a>思路逻辑</h3><ol>
<li>自定义标签控件 TabItemView，对应布局 view_tab_item.xml，用于显示标签名称以及图标，预设 “选中” 与 “未选中” 两种状态；</li>
<li>自定义底部标签栏 BottomTabView，用来存放 TabItemView，并设置监听事件动态改变其状态，暴露出绑定 Viewpager 的方法；</li>
<li>主页面布局文件（ViewPager + BottomTabView）；</li>
<li>Activity中 BottomTabView 关联绑定 Viewpager；  </li>
<li>ViewPager 绑定Fragment 列表，并设置 PageChangeListener。</li>
</ol>
<p>整体逻辑大概如上所述，当然，自定义控件项目中已经写好，直接复制过来用就好了</p>
<h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><ol>
<li>从项目中复制文件BottomTabView.java，view_tab_item.xml 到对应目录下</li>
<li>搭建主页面布局文件（ViewPager + BottomTabView）<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">LinearLayout</span> <span class="attr">xmlns:android</span>=<span class="string">&quot;http://schemas.android.com/apk/res/android&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:layout_height</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:orientation</span>=<span class="string">&quot;vertical&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">android.support.v4.view.ViewPager</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:id</span>=<span class="string">&quot;@+id/viewPager&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_height</span>=<span class="string">&quot;0dp&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_weight</span>=<span class="string">&quot;1&quot;</span> /&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 改为你的对应目录 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">top.vchao.hnust.view.BottomTabView</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:id</span>=<span class="string">&quot;@+id/bottomTabView&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_height</span>=<span class="string">&quot;50dp&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:gravity</span>=<span class="string">&quot;bottom&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">LinearLayout</span>&gt;</span></span><br></pre></td></tr></table></figure></li>
<li>初始化 Fragment列表 并与 ViewPager 绑定；<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化 fragment 列表</span></span><br><span class="line">List&lt;Fragment&gt; fragments = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">fragments.add(<span class="keyword">new</span> TabHomeFragment());</span><br><span class="line">fragments.add(<span class="keyword">new</span> TabNewsFragment());</span><br><span class="line">fragments.add(<span class="keyword">new</span> TabMyFragment());</span><br><span class="line"></span><br><span class="line">adapter = <span class="keyword">new</span> FragmentPagerAdapter(getSupportFragmentManager()) &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Fragment <span class="title">getItem</span><span class="params">(<span class="keyword">int</span> position)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> fragments.get(position);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getCount</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> fragments.size();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">viewPager.setAdapter(adapter);</span><br></pre></td></tr></table></figure></li>
<li>初始化标签列表，并将 BottomTabView 与 Viewpager 关联绑定。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">List&lt;BottomTabView.TabItemView&gt; tabItemViews = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">tabItemViews.add(<span class="keyword">new</span> BottomTabView.TabItemView(<span class="keyword">this</span>, <span class="string">&quot;首页&quot;</span>, R.color.colorPrimary,</span><br><span class="line">                R.color.colorAccent, R.mipmap.main_home_nor, R.mipmap.main_home_pre));</span><br><span class="line">tabItemViews.add(<span class="keyword">new</span> BottomTabView.TabItemView(<span class="keyword">this</span>, <span class="string">&quot;新闻&quot;</span>, R.color.colorPrimary,</span><br><span class="line">                R.color.colorAccent, R.mipmap.main_buy_nor, R.mipmap.main_buy_pre));</span><br><span class="line">tabItemViews.add(<span class="keyword">new</span> BottomTabView.TabItemView(<span class="keyword">this</span>, <span class="string">&quot;我的&quot;</span>, R.color.colorPrimary,</span><br><span class="line">                R.color.colorAccent, R.mipmap.main_user_nor, R.mipmap.main_user_pre));</span><br><span class="line"></span><br><span class="line">bottomTabView.setTabItemViews(tabItemViews);</span><br><span class="line">bottomTabView.setUpWithViewPager(viewPager);</span><br></pre></td></tr></table></figure></li>
</ol>
<p>至此，主页面搭建完毕。<br>（项目代码正在整理，估计将会在一周内放到 github 上，等源码的同学稍安勿躁哈~~）</p>
<p>如果本文对你有所帮助，还望可以随手赏一个点赞哈 ~ ~</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%A6%E4%BD%A0%E6%92%B8%E4%B8%80%E4%B8%AA%E6%A0%A1%E5%9B%ADAPP%EF%BC%88%E5%85%AD%EF%BC%89%EF%BC%9A%E5%A4%B1%E7%89%A9%E6%8B%9B%E9%A2%86&%E4%BA%8C%E6%89%8B%E4%BA%A4%E6%98%93%E6%A8%A1%E5%9D%97/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%A6%E4%BD%A0%E6%92%B8%E4%B8%80%E4%B8%AA%E6%A0%A1%E5%9B%ADAPP%EF%BC%88%E5%85%AD%EF%BC%89%EF%BC%9A%E5%A4%B1%E7%89%A9%E6%8B%9B%E9%A2%86&%E4%BA%8C%E6%89%8B%E4%BA%A4%E6%98%93%E6%A8%A1%E5%9D%97/" class="post-title-link" itemprop="url">手把手带你撸一个校园APP（六）：失物招领&二手交易模块</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <blockquote>
<p>代码经过简单的整理，已经放到Github上了。<a target="_blank" rel="noopener" href="https://github.com/zhengweichao/Hevttc">https://github.com/zhengweichao/Hevttc</a><br>回首来看，代码质量一般，里面也有各种逻辑问题，还望各位看官海涵。接下来有时间的话，会逐步进行优化。如果对你有帮助，还望可以给个star，不胜感激。</p>
</blockquote>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在校园生活中，存在着各种各样的信息差。也正因为这些信息差的存在，经常让校园同学们面临各种各样的问题。比如丢东西的找不到东西，捡到东西的找不到失主等等。其中最为突出的莫过于 【失物招领】及【二手交易】了。<br>校园作为一个比较特殊的地点，大家的素质以及诚信度普遍高于其它地方。“科师有约” 校园APP致力于减少校园信息差的存在，让校园生活更简单。</p>
<blockquote>
<p>科师有约 只是一个美好的想法，并将部分功能从技术上进行简单实现。在实际生活中并未推广开来使用，在此权当抛砖引玉之用。还是希望学弟学妹们可以做出真正的产品，并在校园生活中得以应用。</p>
</blockquote>
<p>本项目基于Bmob进行开发，很多地方需要Bmob相关知识。大家如果没了解过的话，可先自行查看 <a target="_blank" rel="noopener" href="http://doc.bmob.cn/data/android/develop_doc/">Bmob开发文档</a> 。</p>
<p><strong>系列文章：</strong></p>
<p>Github地址： <a target="_blank" rel="noopener" href="https://github.com/zhengweichao/Hevttc">科师有约校园APP</a></p>
<ol>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/80787263">手把手带你撸一个校园APP（一）：项目简介</a></li>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/81456617">手把手带你撸一个校园APP（二）：应用启动和欢迎页面</a></li>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/81837608">手把手带你撸一个校园APP（三）：用户模块（登录注册等）</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/zheng_weichao/article/details/104209803">手把手带你撸一个校园APP（四）：APP框架及功能设计</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/zheng_weichao/article/details/104227390">手把手带你撸一个校园APP（五）：新闻页面中心模块</a></li>
<li>手把手带你撸一个校园APP（六）：失物招领&amp;二手交易模块</li>
<li>手把手带你撸一个校园APP（七）：图说校园模块</li>
<li>手把手带你撸一个校园APP（八）：校园通讯录模块</li>
<li>手把手带你撸一个校园APP（九）：课程表模块（模拟登陆爬取教务处课程信息）</li>
<li>手把手带你撸一个校园APP（十）：APP通用模块（更新，意见反馈等）</li>
</ol>
<h2 id="实现效果"><a href="#实现效果" class="headerlink" title="实现效果"></a>实现效果</h2><p>【失物招领】：<br>分为 寻物启事（找东西的）+ 失物招领（找失主的）<br>【二手交易】：<br>分为 淘点宝贝（想买东西的）+ 换点银子（想卖东西的）。效果图与失物招领类似</p>
<p><img src="https://img-blog.csdnimg.cn/20200211124046605.gif" alt="在这里插入图片描述"></p>
<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>失物招领与二手交易，两个模块从整体逻辑上来说基本一致。因为我们只做简易实现，所以就两个模块就不再区分讲解（下简称 “需求” ）。<br>（当然，这里还可以做很多很多优化，本文后面【未来可做的优化】部分会进一步讲解）<br>需要实现功能如下：</p>
<ol>
<li>发布需求（含 图片压缩及上传）</li>
<li>需求列表展示</li>
<li>需求详情展示</li>
<li>沟通联系（简化为 拨打电话）</li>
</ol>
<p>其中需求列表的展示页面，与 新闻中心模块 使用的技术代码基本相同。想了解的可以再退回去看 <a target="_blank" rel="noopener" href="https://blog.csdn.net/zheng_weichao/article/details/104227390">上篇文章</a>。 </p>
<h2 id="数据库设计"><a href="#数据库设计" class="headerlink" title="数据库设计"></a>数据库设计</h2><p>（因两模块基本逻辑相似，仅就失物招领模块开讲）<br>为了使数据区分的更清楚，设计数据库时，将 “失物招领” 与 “寻物启事” 分别设置为两个表。其中的字段名称设计等均相同。字段如下：</p>
<table>
<thead>
<tr>
<th>字段名</th>
<th>描述</th>
<th>类型</th>
<th>是否主键</th>
</tr>
</thead>
<tbody><tr>
<td>objectId</td>
<td>事件id号</td>
<td>String</td>
<td>是</td>
</tr>
<tr>
<td>title</td>
<td>事件标题</td>
<td>String</td>
<td>-</td>
</tr>
<tr>
<td>tel</td>
<td>联系方式</td>
<td>Number</td>
<td>-</td>
</tr>
<tr>
<td>author</td>
<td>发布人</td>
<td>String</td>
<td>-</td>
</tr>
<tr>
<td>content</td>
<td>事件详细描述</td>
<td>String</td>
<td>-</td>
</tr>
<tr>
<td>isOver</td>
<td>事件处理状态</td>
<td>Boolean</td>
<td>-</td>
</tr>
<tr>
<td>createdAt</td>
<td>事件发布时间</td>
<td>Date</td>
<td>-</td>
</tr>
</tbody></table>
<p>（大家也可以设置为一个表，其中另外加一个字段进行区分） </p>
<h2 id="安卓实现"><a href="#安卓实现" class="headerlink" title="安卓实现"></a>安卓实现</h2><h3 id="发布需求"><a href="#发布需求" class="headerlink" title="发布需求"></a>发布需求</h3><p>此处应用的技术都比较简单。发布需求 其实就是简单的数据库的增加操作。带图片的需求需要再做一下进一步的处理（图片压缩以及上传）</p>
<h4 id="发布无图片的需求"><a href="#发布无图片的需求" class="headerlink" title="发布无图片的需求"></a>发布无图片的需求</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">LoseItem bean = <span class="keyword">new</span> LoseItem();</span><br><span class="line">MyUser user = BmobUser.getCurrentUser(MyUser.class);</span><br><span class="line">bean.setAuthor(user.getUsername());</span><br><span class="line">bean.setContent(loseDesc);</span><br><span class="line">bean.setTel(loseTel);</span><br><span class="line">bean.setTitle(loseTitle);</span><br><span class="line">bean.save(<span class="keyword">new</span> SaveListener&lt;String&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">done</span><span class="params">(String objectId, BmobException e)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="keyword">null</span>) &#123;</span><br><span class="line">            ToastUtil.show(LoseAddActivity.<span class="keyword">this</span>, <span class="string">&quot;发布成功&quot;</span>, Toast.LENGTH_SHORT);</span><br><span class="line">            LoadDialog.dismiss(LoseAddActivity.<span class="keyword">this</span>);</span><br><span class="line">            startActivity(<span class="keyword">new</span> Intent(LoseAddActivity.<span class="keyword">this</span>, LoseActivity.class));</span><br><span class="line">            finish();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ToastUtil.show(LoseAddActivity.<span class="keyword">this</span>, <span class="string">&quot;发布失败，请稍后再试&quot;</span>, Toast.LENGTH_SHORT);</span><br><span class="line">            LoadDialog.dismiss(LoseAddActivity.<span class="keyword">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h4 id="发布有图片的需求"><a href="#发布有图片的需求" class="headerlink" title="发布有图片的需求"></a>发布有图片的需求</h4><p>现在手机像素一般都很高，所以一个图片好几MB 的情况也很常见。直接上传的话，会极大浪费用户流量以及文件存储空间。所以需要对图片进行压缩处理后再上传。<br>所以发布有图片的需求需要拆分为以下 4 步来进行。</p>
<p><strong>1. 选择图片</strong><br>选择图片部分使用了 廖子尧 大神的 imagepicker 库。完全仿微信UI，实现了拍照、图片选择（单选/多选）、 裁剪 、旋转 等功能。</p>
<blockquote>
<p>Github : <a target="_blank" rel="noopener" href="https://github.com/jeasonlzy/ImagePicker">imagepicker</a><br>作者：廖子尧  jeasonlzy</p>
</blockquote>
<p>大神的文档写的十分详尽，集成方法我就不再赘述了。核心代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置 图片选择器 相应参数</span></span><br><span class="line">ImagePicker imagePicker = ImagePicker.getInstance();</span><br><span class="line">imagePicker.setImageLoader(<span class="keyword">new</span> GlideImageLoader());   <span class="comment">//设置图片加载器</span></span><br><span class="line">imagePicker.setShowCamera(<span class="keyword">true</span>);                      <span class="comment">//显示拍照按钮</span></span><br><span class="line">imagePicker.setCrop(<span class="keyword">true</span>);                           <span class="comment">//允许裁剪（单选才有效）</span></span><br><span class="line">imagePicker.setSaveRectangle(<span class="keyword">true</span>);                   <span class="comment">//是否按矩形区域保存</span></span><br><span class="line">imagePicker.setSelectLimit(maxImgCount);              <span class="comment">//选中数量限制</span></span><br><span class="line">imagePicker.setStyle(CropImageView.Style.RECTANGLE);  <span class="comment">//裁剪框的形状</span></span><br><span class="line">imagePicker.setFocusWidth(<span class="number">800</span>);                       <span class="comment">//裁剪框的宽度。单位像素（圆形自动取宽高最小值）</span></span><br><span class="line">imagePicker.setFocusHeight(<span class="number">800</span>);                      <span class="comment">//裁剪框的高度。单位像素（圆形自动取宽高最小值）</span></span><br><span class="line">imagePicker.setOutPutX(<span class="number">1000</span>);                         <span class="comment">//保存文件的宽度。单位像素</span></span><br><span class="line">imagePicker.setOutPutY(<span class="number">1000</span>);                         <span class="comment">//保存文件的高度。单位像素</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ...省略其它代码</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 开始选择图片</span></span><br><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;拍照&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;相册&quot;</span>);</span><br><span class="line">showDialog(<span class="keyword">new</span> SelectDialog.SelectDialogListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onItemClick</span><span class="params">(AdapterView&lt;?&gt; parent, View view, <span class="keyword">int</span> position, <span class="keyword">long</span> id)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">switch</span> (position) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">                <span class="comment">// 直接调起相机</span></span><br><span class="line">                ImagePicker.getInstance().setSelectLimit(maxImgCount - selImageList.size());</span><br><span class="line">                Intent intent = <span class="keyword">new</span> Intent(LoseAddActivity.<span class="keyword">this</span>, ImageGridActivity.class);</span><br><span class="line">                intent.putExtra(ImageGridActivity.EXTRAS_TAKE_PICKERS, <span class="keyword">true</span>); <span class="comment">// 是否是直接打开相机</span></span><br><span class="line">                startActivityForResult(intent, REQUEST_CODE_SELECT);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">                <span class="comment">//打开选择,本次允许选择的数量</span></span><br><span class="line">                ImagePicker.getInstance().setSelectLimit(maxImgCount - selImageList.size());</span><br><span class="line">                Intent intent1 = <span class="keyword">new</span> Intent(LoseAddActivity.<span class="keyword">this</span>, ImageGridActivity.class);</span><br><span class="line">                intent1.putExtra(ImageGridActivity.EXTRAS_IMAGES, images);</span><br><span class="line">                startActivityForResult(intent1, REQUEST_CODE_SELECT);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, names);</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><strong>2. 图片压缩</strong><br>图片压缩部分采用了 Luban（鲁班） —— Android图片压缩工具，仿微信朋友圈压缩策略。<br>作者采用逆向推算，效果已经很接近微信朋友圈压缩后的效果。<br>（再次踩在了巨人的肩膀上……）</p>
<blockquote>
<p>Github :  <a target="_blank" rel="noopener" href="https://github.com/Curzibn/Luban">Luban（鲁班）</a><br>作者： Curzibn</p>
</blockquote>
<p>核心代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Luban.with(LoseAddActivity.<span class="keyword">this</span>)</span><br><span class="line">     .load(paths)                                   <span class="comment">// 传入要压缩的图片列表</span></span><br><span class="line">     .ignoreBy(<span class="number">100</span>)                                  <span class="comment">// 忽略不压缩图片的大小</span></span><br><span class="line">     .setTargetDir(getPath())                        <span class="comment">// 设置压缩后文件存储位置</span></span><br><span class="line">     .setCompressListener(<span class="keyword">new</span> OnCompressListener() &#123; <span class="comment">//设置回调</span></span><br><span class="line">         <span class="meta">@Override</span></span><br><span class="line">         <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onStart</span><span class="params">()</span> </span>&#123;</span><br><span class="line">             <span class="comment">// 开始压缩 ...</span></span><br><span class="line">         &#125;</span><br><span class="line">         <span class="meta">@Override</span></span><br><span class="line">         <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSuccess</span><span class="params">(File file)</span> </span>&#123;</span><br><span class="line">             <span class="comment">// 压缩成功 ...</span></span><br><span class="line">         &#125;</span><br><span class="line">         <span class="meta">@Override</span></span><br><span class="line">         <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable e)</span> </span>&#123;</span><br><span class="line">             <span class="comment">// 当压缩过程出现问题时调用</span></span><br><span class="line">         &#125;</span><br><span class="line">      &#125;).launch();    <span class="comment">//启动压缩</span></span><br></pre></td></tr></table></figure>


<p><strong>3. 图片上传</strong><br>这里是按照Bmob官方代码来写的。详情可见 <a target="_blank" rel="noopener" href="http://doc.bmob.cn/data/android/develop_doc/#8">Bmob文件管理</a> 。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">BmobFile.uploadBatch(filePaths, <span class="keyword">new</span> UploadBatchListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSuccess</span><span class="params">(List&lt;BmobFile&gt; files, List&lt;String&gt; urls)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 上传图片成功</span></span><br><span class="line">        <span class="comment">//1、files-上传完成后的BmobFile集合，是为了方便大家对其上传后的数据进行操作，例如你可以将该文件保存到表中</span></span><br><span class="line">        <span class="comment">//2、urls-上传文件的完整url地址</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(<span class="keyword">int</span> statuscode, String errormsg)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 上传图片失败</span></span><br><span class="line">        LoadDialog.dismiss(LoseAddActivity.<span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProgress</span><span class="params">(<span class="keyword">int</span> curIndex, <span class="keyword">int</span> curPercent, <span class="keyword">int</span> total, <span class="keyword">int</span> totalPercent)</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>需要注意的是 <strong>上传多张图片，onSuccess 会多次调用</strong> 。别忘了做相应处理。</p>
<p><strong>4. 发布需求</strong><br>和发布无图片需求代码基本一致，只不过多了一句设置图片的代码。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">LoseItem bean = <span class="keyword">new</span> LoseItem();</span><br><span class="line"><span class="comment">// ... 省略其它代码</span></span><br><span class="line">bean.setPic(files.get(<span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">bean.save(<span class="keyword">new</span> SaveListener&lt;String&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">done</span><span class="params">(String objectId, BmobException e)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// ... 省略其它代码</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>


<p>至此，失物招领 &amp; 二手交易模块基本完成。</p>
<p>然而整个流程中还有很多可以优化的地方，作者大概率没有时间和精力去做这部分了。<br>特将这部分内容整理如下，若他人有类似需求，以望能有所启迪或帮助。</p>
<h2 id="未来可做的优化"><a href="#未来可做的优化" class="headerlink" title="未来可做的优化"></a>未来可做的优化</h2><p>简而言之，就是 <strong>提升平台能力</strong>。</p>
<p>因两个模块逻辑相似性较高，故此处以【失物招领】为例：<br>在一个完整的过程中，总共存在3方：丢东西的，捡东西的，平台。</p>
<p>本文上面已经讲解了最基础简易的沟通流程：</p>
<ol>
<li>丢东西的 在平台发布 寻物启事；捡东西的看见 –&gt; 私下联系；</li>
<li>捡东西的 在平台发布 失物招领；丢东西的看见 –&gt; 私下联系；</li>
</ol>
<p>这种方式固然简单粗暴，但是显得不太灵活。<br>身为平台，应该发挥出平台更大的能力。那么应该从哪里开始入手呢？</p>
<p>校园是一个很特殊的地方，这里的失物招领有许多特殊之处，总结一下不难发现：</p>
<ol>
<li>用户群体（学生，教职工，家属楼住户）绝大多数都接受过高等教育，文化素质较高，认知较为统一； ==&gt; 【其它用户捡到后，有较大概率归还】</li>
<li>用户活动范围较为一致，活动区域较为封闭； ==&gt;【用户丢失物品，较大概率被其它校园用户捡到】</li>
<li>用户捡到或丢失的物品有较高重复性（校园卡，身份证，课本）； ==&gt; 【物品上一般带有 失主信息，可以通过实名信息直接锁定失主】</li>
<li>根据院系，专业，班级等，很容易找到相关实名用户。 ==&gt; 【根据附属信息，很容易找到某人或其朋友同学】</li>
<li>有很多人捡到东西后，会交给专门的组织或个人（失物招领协会，门卫，校园卡服务中心等）。 ==&gt; 【可以集中起来这些地方的信息，统一处理】</li>
</ol>
<p>综上所述，未来如果有学弟学妹想做这部分，按个人看法，可做的优化如下：</p>
<ol>
<li>加强用户实名认证操作，仅限实名认证用户使用APP。</li>
<li>定期收集整理线下失物招领信息，录入线上系统。</li>
<li>线上系统运用实名信息匹配失主并通过推送私信等方式进行联系（保证用户隐私的前提下进行）。</li>
<li>对线上信息进行匹配以及相似内容匹配操作，以优化处理双方都发布了信息的情况。</li>
</ol>
<hr>
<p>至此，本文内容全部完毕，项目中大量依赖或采用了第三方项目的代码。在此向各位有分享精神的大佬们致敬！</p>
<p>如果本文对你有所帮助，还望可以随手赏一个点赞哈 ~ ~</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://vchao.top/2021/08/25/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%A6%E4%BD%A0%E6%92%B8%E4%B8%80%E4%B8%AA%E6%A0%A1%E5%9B%ADAPP%EF%BC%88%E5%85%AB%EF%BC%89%EF%BC%9A%E6%A0%A1%E5%9B%AD%E9%80%9A%E8%AE%AF%E5%BD%95%E6%A8%A1%E5%9D%97/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="vchao">
      <meta itemprop="description" content="怀揣好奇，梦想改变世界的菜鸟极客">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="乘风破浪">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/08/25/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%A6%E4%BD%A0%E6%92%B8%E4%B8%80%E4%B8%AA%E6%A0%A1%E5%9B%ADAPP%EF%BC%88%E5%85%AB%EF%BC%89%EF%BC%9A%E6%A0%A1%E5%9B%AD%E9%80%9A%E8%AE%AF%E5%BD%95%E6%A8%A1%E5%9D%97/" class="post-title-link" itemprop="url">手把手带你撸一个校园APP（八）：校园通讯录模块</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2021-08-25 22:42:51 / 修改时间：22:37:20" itemprop="dateCreated datePublished" datetime="2021-08-25T22:42:51+08:00">2021-08-25</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android/" itemprop="url" rel="index"><span itemprop="name">Android</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>校园通讯录功能，收集校园内的公开联系方式等内容，并进行展示。在日常生活中经常可能需要用到。比如 水电处问题了找水电师傅，招生就业办，学工办的电话等等。</p>
<blockquote>
<p>当然，出于隐私考虑，个人手机号不会被收集展示。只展示办公室联系方式等可在网上或校园内查到的号码。</p>
</blockquote>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>校园通讯录模块旨在打造便捷的校园信息服务平台，完善校园信息服务，给校园信息的管理和维护提供更便捷、更安全的服务系统。<br>（本文对该模块进行简易实现，仅讲述通讯录列表样式的展示）</p>
<h4 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h4><p>Github地址： <a target="_blank" rel="noopener" href="https://github.com/zhengweichao/Hevttc">科师有约校园APP</a></p>
<ol>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/80787263">手把手带你撸一个校园APP（一）：项目简介</a></li>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/81456617">手把手带你撸一个校园APP（二）：应用启动和欢迎页面</a></li>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/81837608">手把手带你撸一个校园APP（三）：用户模块（登录注册等）</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/zheng_weichao/article/details/104209803">手把手带你撸一个校园APP（四）：APP框架及功能设计</a></li>
<li><a target="_blank" rel="noopener" href="https://blog.csdn.net/zheng_weichao/article/details/104227390">手把手带你撸一个校园APP（五）：新闻页面中心模块</a></li>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/104245390">手把手带你撸一个校园APP（六）：失物招领&amp;二手交易模块</a></li>
<li><a target="_blank" rel="noopener" href="https://vchao.blog.csdn.net/article/details/104273994">手把手带你撸一个校园APP（七）：校园文化模块（社团活动&amp;表白墙&amp;图说校园）</a></li>
<li><strong>手把手带你撸一个校园APP（八）：校园通讯录模块</strong></li>
<li>手把手带你撸一个校园APP（九）：课程表模块（模拟登陆爬取教务处课程信息）</li>
<li>手把手带你撸一个校园APP（十）：APP通用模块（更新，意见反馈等）</li>
</ol>
<h2 id="实现效果"><a href="#实现效果" class="headerlink" title="实现效果"></a>实现效果</h2><p>校园通讯录：<br><img src="https://img-blog.csdnimg.cn/20200219225503419.gif" alt="校园通讯录"></p>
<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><ol>
<li>校园通讯录数据部分比较简单，总共 姓名称呼，部门，联系方式 3个主要字段即可；</li>
<li>通讯录列表的数据需要排序分组后再显示；<blockquote>
<p>  <strong>排序</strong>：</p>
<pre><code> 按照姓名称呼的首字母拼音排序 
</code></pre>
<p>  ==&gt; 需要获取首字的拼音 </p>
<pre><code>  ==&gt; 通过三方库 [TinyPinyin](https://link.jianshu.com?t=https://github.com/promeG/TinyPinyin) 来实现
  **分组**：
  可以通过自定义 RecycleView 的 ItemDecoration 来实现，需要额外处理悬浮窗的显示情况
</code></pre>
</blockquote>
</li>
<li>右侧显示字母快速索引列表，并关联 RecycleView ，可以关联滑动<blockquote>
<p>通过自定义控件来实现</p>
</blockquote>
</li>
</ol>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p><strong>说明</strong>：本模块的实现主要参考 <em>小马快跑</em> 的 仿魅族通讯录 项目，并在其基础上进行了适合本项目的优化，在此向原作者致敬。</p>
<blockquote>
<p>Github：<a target="_blank" rel="noopener" href="https://link.jianshu.com/?t=https://github.com/crazyqiang/AndroidStudy/blob/master/app/src/main/java/org/ninetripods/mq/study/recycle/ContactsActivity.java">仿魅族通讯录</a><br>作者： <em>小马快跑</em><br>文章：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/7b7b7ee80c44">https://www.jianshu.com/p/7b7b7ee80c44</a></p>
</blockquote>
<h3 id="数据库设计"><a href="#数据库设计" class="headerlink" title="数据库设计"></a>数据库设计</h3><table>
<thead>
<tr>
<th>字段名</th>
<th>描述</th>
<th>类型</th>
<th>是否主键</th>
</tr>
</thead>
<tbody><tr>
<td>objectId</td>
<td>唯一标识</td>
<td>String</td>
<td>是</td>
</tr>
<tr>
<td>name</td>
<td>姓名称呼</td>
<td>String</td>
<td>-</td>
</tr>
<tr>
<td>department</td>
<td>部门</td>
<td>String</td>
<td>-</td>
</tr>
<tr>
<td>tel</td>
<td>联系方式</td>
<td>String</td>
<td>-</td>
</tr>
</tbody></table>
<h3 id="安卓实现"><a href="#安卓实现" class="headerlink" title="安卓实现"></a>安卓实现</h3><p>下面介绍下校园通讯录模块实现的主要思路：</p>
<h4 id="step-1-数据排序"><a href="#step-1-数据排序" class="headerlink" title="step 1:  数据排序"></a>step 1:  数据排序</h4><p>依赖 TinyPinyin 库后，对源数据进行加工处理，增加设置 indexTag 字段内容。然后依据该字段进行排序；</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sortData</span><span class="params">(List&lt;Teacher&gt; list)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (list == <span class="keyword">null</span> || list.size() == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; list.size(); i++) &#123;</span><br><span class="line">        Teacher bean = list.get(i);</span><br><span class="line">        String tag = Pinyin.toPinyin(bean.getName().substring(<span class="number">0</span>, <span class="number">1</span>).charAt(<span class="number">0</span>)).substring(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (tag.matches(<span class="string">&quot;[A-Z]&quot;</span>)) &#123;</span><br><span class="line">            bean.setIndexTag(tag);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            bean.setIndexTag(<span class="string">&quot;#&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    Collections.sort(list, <span class="keyword">new</span> Comparator&lt;Teacher&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">compare</span><span class="params">(Teacher o1, Teacher o2)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;#&quot;</span>.equals(o1.getIndexTag())) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;#&quot;</span>.equals(o2.getIndexTag())) &#123;</span><br><span class="line">                <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> o1.getIndexTag().compareTo(o2.getIndexTag());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br></pre></td></tr></table></figure>
<h4 id="step-2-数据分组"><a href="#step-2-数据分组" class="headerlink" title="step 2:  数据分组"></a>step 2:  数据分组</h4><p>数据排序完成后，通过自定义 ItemDecoration 来绘制悬浮框及 ItemView 之上的分类Tag</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//用来绘制每个ItemView的边距</span></span><br><span class="line">@Override</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getItemOffsets</span>(<span class="params">Rect outRect, View view, RecyclerView parent, RecyclerView.State state</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//............省略部分代码............</span></span><br><span class="line">    <span class="built_in">int</span> position = parent.getChildAdapterPosition(view);</span><br><span class="line">    <span class="keyword">if</span> (position == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//第一条数据有bar</span></span><br><span class="line">        outRect.<span class="keyword">set</span>(<span class="number">0</span>, dividerHeight, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (position &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (TextUtils.isEmpty(mBeans.<span class="keyword">get</span>(position).getIndexTag())) <span class="keyword">return</span>;</span><br><span class="line">        <span class="comment">//与上一条数据中的tag不同时，该显示bar了</span></span><br><span class="line">        <span class="keyword">if</span> (!mBeans.<span class="keyword">get</span>(position).getIndexTag().<span class="keyword">equals</span>(mBeans.<span class="keyword">get</span>(position - <span class="number">1</span>).getIndexTag())) &#123;</span><br><span class="line">            outRect.<span class="keyword">set</span>(<span class="number">0</span>, dividerHeight, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//用来绘制最上面的悬浮框</span></span><br><span class="line">@Override</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDrawOver</span>(<span class="params">Canvas canvas, RecyclerView parent, RecyclerView.State state</span>)</span> &#123;</span><br><span class="line">     <span class="built_in">int</span> position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();</span><br><span class="line">     final <span class="built_in">int</span> bottom = parent.getPaddingTop() + dividerHeight;</span><br><span class="line">     mPaint.setColor(Color.WHITE);</span><br><span class="line">     <span class="comment">//绘制悬浮框的范围</span></span><br><span class="line">     canvas.drawRect(parent.getLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + dividerHeight, mPaint);</span><br><span class="line">     <span class="comment">//............省略部分代码............</span></span><br><span class="line">     mPaint.setTextSize(<span class="number">40</span>);</span><br><span class="line">     canvas.drawCircle(DpUtil.dp2px(mContext, <span class="number">42.5f</span>), bottom - dividerHeight / <span class="number">2</span>, <span class="number">35</span>, mPaint);</span><br><span class="line">     mPaint.setColor(Color.WHITE);</span><br><span class="line">     canvas.drawText(mBeans.<span class="keyword">get</span>(position).getIndexTag(), DpUtil.dp2px(mContext, <span class="number">42.5f</span>), bottom - dividerHeight / <span class="number">3</span>, mPaint);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//按需绘制ItemView上面的分类Tag</span></span><br><span class="line">@Override</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDraw</span>(<span class="params">Canvas canvas, RecyclerView parent, RecyclerView.State state</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//............省略部分代码............</span></span><br><span class="line">    final <span class="built_in">int</span> childCount = parent.getChildCount();</span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; childCount; i++) &#123;</span><br><span class="line">        final View child = parent.getChildAt(i);</span><br><span class="line">        final RecyclerView.LayoutParams <span class="keyword">params</span> = (RecyclerView.LayoutParams) child.getLayoutParams();</span><br><span class="line">        <span class="built_in">int</span> position = <span class="keyword">params</span>.getViewLayoutPosition();</span><br><span class="line">        <span class="keyword">if</span> (position == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//第一条数据有bar</span></span><br><span class="line">            drawTitleBar(canvas, parent, child, mBeans.<span class="keyword">get</span>(position), tagsStr.indexOf(mBeans.<span class="keyword">get</span>(position).getIndexTag()));</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (position &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//与上一条数据中的tag不同时，该显示bar了</span></span><br><span class="line">            <span class="keyword">if</span> (!mBeans.<span class="keyword">get</span>(position).getIndexTag().<span class="keyword">equals</span>(mBeans.<span class="keyword">get</span>(position - <span class="number">1</span>).getIndexTag())) &#123;</span><br><span class="line">                drawTitleBar(canvas, parent, child, mBeans.<span class="keyword">get</span>(position), tagsStr.indexOf(mBeans.<span class="keyword">get</span>(position).getIndexTag()));</span><br><span class="line">            &#125; &#125; </span><br><span class="line">     &#125;  </span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<h4 id="step-3-自定义右侧索引View"><a href="#step-3-自定义右侧索引View" class="headerlink" title="step 3:  自定义右侧索引View"></a>step 3:  自定义右侧索引View</h4><ul>
<li><strong>绘制右侧导航栏字母：</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDraw</span><span class="params">(Canvas canvas)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//for循环绘制出所有的导航栏字母</span></span><br><span class="line">     <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; indexStr.length(); i++) &#123;</span><br><span class="line">         String textTag = indexStr.substring(i, i + <span class="number">1</span>);</span><br><span class="line">         <span class="keyword">float</span> xPos = (mWidth - mPaint.measureText(textTag)) / <span class="number">2</span>;</span><br><span class="line">         canvas.drawText(textTag, xPos, singleHeight * (i + <span class="number">1</span>) + DpUtil.dp2px(mContext, TOP_MARGIN), mPaint);</span><br><span class="line">     &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>处理滑动事件:</strong></li>
</ul>
<p>在onTouchEvent处理了滑动事件，当手指上下滑动时左侧有个圆跟着滑动，这里用的自定义IndexBar( IndexBar extends ViewGroup，IndexBar包含SideBar )来处理的，当SideBar滑动处于MOVE状态时通过((IndexBar) getParent()).setDrawData()把一系列位置参数传到IndexBar中去</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onTouchEvent</span><span class="params">(MotionEvent event)</span> </span>&#123;</span><br><span class="line">   <span class="comment">//处理按下滑动事件</span></span><br><span class="line">    <span class="keyword">switch</span> (event.getAction()) &#123;</span><br><span class="line">       <span class="keyword">case</span> MotionEvent.ACTION_DOWN:</span><br><span class="line">            <span class="comment">//按下</span></span><br><span class="line">            mPaint.setColor(Color.BLACK);</span><br><span class="line">            invalidate();</span><br><span class="line">       <span class="keyword">case</span> MotionEvent.ACTION_MOVE:</span><br><span class="line">            <span class="comment">//滑动 event.getY()得到在父View中的Y坐标，通过和总高度的比例再乘以字符个数总长度得到按下的位置</span></span><br><span class="line">            <span class="keyword">int</span> position = (<span class="keyword">int</span>) ((event.getY() - getTop() - DpUtil.dp2px(mContext, <span class="number">80</span>)) / mHeight * indexStr.toCharArray().length);</span><br><span class="line">            <span class="keyword">if</span> (position &gt;= <span class="number">0</span> &amp;&amp; position &lt; indexStr.length()) &#123;</span><br><span class="line">                ((IndexBar) getParent()).setDrawData(event.getY(), String.valueOf(indexStr.toCharArray()[position]), position);</span><br><span class="line">                <span class="keyword">if</span> (listener != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    listener.indexChanged(indexStr.substring(position, position + <span class="number">1</span>));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> MotionEvent.ACTION_UP:</span><br><span class="line">            <span class="comment">//抬起</span></span><br><span class="line">            ((IndexBar) getParent()).setTagStatus(<span class="keyword">false</span>);</span><br><span class="line">            mPaint.setColor(Color.GRAY);</span><br><span class="line">            invalidate();</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>主要是在onLayout中把SideBar排列到最右侧，并在onDraw中根据SideBar传过来的一系列位置参数来不断改变圆的位置，</p>
<blockquote>
<p>这里要注意一下，自定义ViewGroup的onDraw()方法默认是不会调用的，如果想执行onDraw方法，可以通过下面两种方法：<br> 1.设置透明背景：<br> 在构造函数中：setBackgroundColor(Color.TRANSPARENT);<br> 或者在xml中：android:background=”@color/transparent”<br> 2.或者可以在构造函数中添加setWillNotDraw(false);</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onLayout</span><span class="params">(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> l, <span class="keyword">int</span> t, <span class="keyword">int</span> r, <span class="keyword">int</span> b)</span> </span>&#123;</span><br><span class="line">     <span class="keyword">int</span> childNum = getChildCount();</span><br><span class="line">     <span class="keyword">if</span> (childNum &lt;= <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line">     <span class="comment">//得到SideBar</span></span><br><span class="line">     View childView = getChildAt(<span class="number">0</span>);</span><br><span class="line">     childWidth = childView.getMeasuredWidth();</span><br><span class="line">     <span class="comment">//把SideBar排列到最右侧</span></span><br><span class="line">     childView.layout((mWidth - childWidth), <span class="number">0</span>, mWidth, mHeight);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDraw</span><span class="params">(Canvas canvas)</span> </span>&#123;</span><br><span class="line">     <span class="keyword">super</span>.onDraw(canvas);</span><br><span class="line">     <span class="keyword">if</span> (isShowTag) &#123;</span><br><span class="line">         <span class="comment">//根据位置来不断变换Paint的颜色</span></span><br><span class="line">         ColorUtil.setPaintColor(mPaint, position);</span><br><span class="line">         <span class="comment">//绘制圆和文字</span></span><br><span class="line">         canvas.drawCircle((mWidth - childWidth) / <span class="number">2</span>, centerY, circleRadius, mPaint);</span><br><span class="line">         mPaint.setColor(Color.WHITE);</span><br><span class="line">         mPaint.setTextSize(<span class="number">80</span>);</span><br><span class="line">         canvas.drawText(tag, (mWidth - childWidth - mPaint.measureText(tag)) / <span class="number">2</span>, centerY - (mPaint.ascent() + mPaint.descent()) / <span class="number">2</span>, mPaint);</span><br><span class="line">     &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<h4 id="step-4-关联RecycleView"><a href="#step-4-关联RecycleView" class="headerlink" title="step 4: 关联RecycleView"></a>step 4: 关联RecycleView</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">sideBar.setIndexChangeListener(<span class="keyword">new</span> SideBar.indexChangeListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">indexChanged</span><span class="params">(String tag)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (TextUtils.isEmpty(tag) || contactList.size() &lt;= <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; contactList.size(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (tag.equals(contactList.get(i).getIndexTag())) &#123;</span><br><span class="line">                layoutManager.scrollToPositionWithOffset(i, <span class="number">0</span>);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<hr>
<p>至此，校园通讯录模块基本完成。<br>本文例子中用到多个三方库<a target="_blank" rel="noopener" href="https://link.jianshu.com/?t=https://github.com/amulyakhare/TextDrawable">TextDrawable</a>，<a target="_blank" rel="noopener" href="https://link.jianshu.com/?t=https://github.com/promeG/TinyPinyin">TinyPinyin</a> ，<a target="_blank" rel="noopener" href="https://link.jianshu.com/?t=https://github.com/crazyqiang/AndroidStudy/blob/master/app/src/main/java/org/ninetripods/mq/study/recycle/ContactsActivity.java">仿魅族通讯录</a> 等，再次向各位大佬致敬！</p>
<p>如果本文对你有所帮助，还望可以随手赏一个点赞哈 ~ ~</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  
  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/page/5/">5</a><a class="extend next" rel="next" href="/page/2/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">vchao</p>
  <div class="site-description" itemprop="description">怀揣好奇，梦想改变世界的菜鸟极客</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">46</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
        <span class="site-state-item-count">4</span>
        <span class="site-state-item-name">分类</span>
      </div>
      <div class="site-state-item site-state-tags">
        <span class="site-state-item-count">27</span>
        <span class="site-state-item-name">标签</span>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        
  <div class="beian"><a href="https://beian.miit.gov.cn/" rel="noopener" target="_blank">冀ICP备 17022751号-1 </a>
      <img src="https://beian.miit.gov.cn" style="display: inline-block;">
  </div>

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">vchao</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://muse.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/muse.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

</body>
</html>
